package raccoon.plugin.mybatis;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

/**
 * Created by YueXiao on 2016/6/30.
 */
public class BatchParameterHandler implements ParameterHandler {

    private final TypeHandlerRegistry typeHandlerRegistry;

    private final MappedStatement mappedStatement;
    private final  List<Object> parameters;
    private BoundSql boundSql;
    private Configuration configuration;

    public BatchParameterHandler(MappedStatement mappedStatement, List<Object> parameters, BoundSql boundSql) {
        this.mappedStatement = mappedStatement;
        this.configuration = mappedStatement.getConfiguration();
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        this.parameters = parameters;
        this.boundSql = boundSql;
    }

    @Override
    public Object getParameterObject() {
        return parameters;
    }


    @Override
    public void setParameters(PreparedStatement ps) throws SQLException {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {

            for(Object parameter : parameters){
                for (int i = 0; i < parameterMappings.size(); i++) {
                    ParameterMapping parameterMapping = parameterMappings.get(i);
                    if (parameterMapping.getMode() != ParameterMode.OUT) {
                        Object value;
                        String propertyName = parameterMapping.getProperty();
                        if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                            value = boundSql.getAdditionalParameter(propertyName);
                        } else if (this.parameters == null) {
                            value = null;
                        } else if (typeHandlerRegistry.hasTypeHandler(this.parameters.getClass())) {
                            value = this.parameters;
                        } else {
                            MetaObject metaObject = configuration.newMetaObject(parameter);
                            value = metaObject.getValue(propertyName);
                        }
                        TypeHandler typeHandler = parameterMapping.getTypeHandler();
                        JdbcType jdbcType = parameterMapping.getJdbcType();
                        if (value == null && jdbcType == null) {
                            jdbcType = configuration.getJdbcTypeForNull();
                        }
                        try {
                            typeHandler.setParameter(ps, i + 1, value, jdbcType);

                        } catch (TypeException e) {
                            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                        } catch (SQLException e) {
                            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                        }
                    }
                }
                ps.addBatch();
            }
        }
    }
}
